package com.micro.ai.gateway.filter;

import com.micro.ai.commons.constant.ErrorCodeEnum;
import com.micro.ai.gateway.config.SignatureProperties;
import com.micro.ai.gateway.util.ResponseUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.data.redis.core.ReactiveRedisTemplate;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.Base64;
import java.util.List;

/**
 * 请求签名验证过滤器（优化版）
 * 防止请求篡改和重放攻击
 * 支持签名配置动态刷新
 * 
 * 签名算法: HMAC-SHA256(timestamp + nonce + path, appSecret)
 * 
 * 优化点：
 * 1. 使用ResponseUtils统一错误响应
 * 2. 使用SignatureProperties支持从Nacos动态刷新配置
 * 
 * @author micro-ai
 * @since 0.0.1
 * @version 1.3 支持动态刷新
 */
@Slf4j
@Component
public class SignatureFilter implements GlobalFilter, Ordered {

    @Autowired
    private ReactiveRedisTemplate<String, String> redisTemplate;

    @Autowired
    private SignatureProperties signatureProperties;

    // Redis key前缀
    private static final String NONCE_PREFIX = "gateway:signature:nonce:";

    // 模拟的appKey和appSecret映射（生产环境应该从数据库或配置中心获取）
    private static final String DEFAULT_APP_SECRET = "micro-ai-default-app-secret-2024";

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 如果未启用签名验证，直接放行
        if (!signatureProperties.isEnabled()) {
            return chain.filter(exchange);
        }

        ServerHttpRequest request = exchange.getRequest();
        String path = request.getURI().getPath();

        // 检查是否需要跳过签名验证
        if (shouldSkipSignature(path)) {
            return chain.filter(exchange);
        }

        // 提取签名相关的请求头
        String timestamp = request.getHeaders().getFirst("X-Timestamp");
        String nonce = request.getHeaders().getFirst("X-Nonce");
        String signature = request.getHeaders().getFirst("X-Signature");
        String appKey = request.getHeaders().getFirst("X-App-Key");

        // 验证必要参数
        if (!StringUtils.hasText(timestamp) || !StringUtils.hasText(nonce) || 
            !StringUtils.hasText(signature) || !StringUtils.hasText(appKey)) {
            log.warn("签名验证失败：缺少必要参数");
            return ResponseUtils.forbidden(exchange, ErrorCodeEnum.A0007.getCode(), "签名验证失败：缺少签名参数");
        }

        try {
            // 1. 验证时间戳（防止重放攻击）
            long requestTime = Long.parseLong(timestamp);
            long currentTime = System.currentTimeMillis();
            if (Math.abs(currentTime - requestTime) > signatureProperties.getTimeout() * 1000) {
                log.warn("签名验证失败：请求已过期，timestamp={}", timestamp);
                return ResponseUtils.forbidden(exchange, ErrorCodeEnum.A0007.getCode(), "签名验证失败：请求已过期");
            }

            // 2. 验证nonce（防止重复请求）
            return checkNonce(nonce)
                    .flatMap(isUsed -> {
                        if (isUsed) {
                            log.warn("签名验证失败：nonce已使用，nonce={}", nonce);
                            return ResponseUtils.forbidden(exchange, ErrorCodeEnum.A0007.getCode(), "签名验证失败：请求已被使用");
                        }

                        // 3. 验证签名
                        String appSecret = getAppSecret(appKey);
                        String calculatedSignature = calculateSignature(timestamp, nonce, path, appSecret);
                        
                        // 使用calculatedSignature.equals()避免空指针（calculatedSignature不会为null）
                        if (calculatedSignature == null || !calculatedSignature.equals(signature)) {
                            log.warn("签名验证失败：签名不匹配，appKey={}", appKey);
                            return ResponseUtils.forbidden(exchange, ErrorCodeEnum.A0007.getCode(), "签名验证失败");
                        }

                        // 4. 记录nonce（标记为已使用）
                        return recordNonce(nonce)
                                .then(chain.filter(exchange));
                    });

        } catch (Exception e) {
            log.error("签名验证异常：{}", e.getMessage(), e);
            return ResponseUtils.forbidden(exchange, ErrorCodeEnum.A0007.getCode(), "签名验证异常");
        }
    }

    @Override
    public int getOrder() {
        return -870; // 在认证之前执行
    }

    /**
     * 检查是否需要跳过签名验证（支持动态刷新）
     */
    private boolean shouldSkipSignature(String path) {
        List<String> skipPaths = signatureProperties.getSkipSignaturePaths();
        if (skipPaths == null || skipPaths.isEmpty()) {
            return false;
        }
        
        for (String skipPath : skipPaths) {
            if (skipPath.endsWith("**")) {
                String prefix = skipPath.substring(0, skipPath.length() - 2);
                if (path.startsWith(prefix)) {
                    return true;
                }
            } else if (path.equals(skipPath) || path.startsWith(skipPath)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 检查nonce是否已使用
     */
    private Mono<Boolean> checkNonce(String nonce) {
        String key = NONCE_PREFIX + nonce;
        return redisTemplate.hasKey(key)
                .defaultIfEmpty(false)
                .onErrorReturn(false);
    }

    /**
     * 记录nonce
     */
    private Mono<Boolean> recordNonce(String nonce) {
        String key = NONCE_PREFIX + nonce;
        return redisTemplate.opsForValue()
                .set(key, "1", Duration.ofSeconds(signatureProperties.getTimeout()))
                .defaultIfEmpty(true)
                .onErrorReturn(true);
    }

    /**
     * 获取appSecret（实际应从配置中心或数据库获取）
     */
    private String getAppSecret(String appKey) {
        // TODO: 从配置中心或数据库获取appSecret
        return DEFAULT_APP_SECRET;
    }

    /**
     * 计算签名
     * 签名算法: HMAC-SHA256(timestamp + nonce + path, appSecret)
     */
    private String calculateSignature(String timestamp, String nonce, String path, String appSecret) {
        try {
            String data = timestamp + nonce + path;
            Mac mac = Mac.getInstance("HmacSHA256");
            SecretKeySpec secretKeySpec = new SecretKeySpec(appSecret.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
            mac.init(secretKeySpec);
            byte[] hmac = mac.doFinal(data.getBytes(StandardCharsets.UTF_8));
            return Base64.getEncoder().encodeToString(hmac);
        } catch (Exception e) {
            log.error("计算签名失败：{}", e.getMessage(), e);
            return "";
        }
    }
}

